/**
 * section: 	XPath
 * synopsis: 	Load a document, locate subelements with XPath, modify
 *              said elements and save the resulting document.
 * purpose: 	Shows how to make a full round-trip from a load/edit/save
 * usage:	xpath2 <xml-file> <xpath-expr> <new-value>
 * test:	xpath2 test3.xml '//discarded' discarded > xpath2.tmp && diff xpath2.tmp $(srcdir)/xpath2.res
 * author: 	Aleksey Sanin and Daniel Veillard
 * copy: 	see Copyright for the status of this software.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

#if defined(LIBXML_XPATH_ENABLED) && defined(LIBXML_SAX1_ENABLED) && \
    defined(LIBXML_OUTPUT_ENABLED)

static void usage(const char *name);
static int example4(const char *filename, const xmlChar *xpathExpr,
                    const xmlChar *value);
static void update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar *value);

/**
 * print_element_names:
 * @a_node: the initial xml node to consider.
 *
 * Prints the names of the all the xml elements
 * that are siblings or children of a given xml node.
 */
static void
print_element_names(xmlNode *a_node, xmlXPathContextPtr xpathCtx)
{
    xmlNode *cur_node = NULL;
    xmlNs *cur_ns = NULL;

    for (cur_node = a_node; cur_node; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_ELEMENT_NODE)
        {
            printf("node type: Element, name: %s\n", cur_node->name);
            // 看看是不是Envelope
            if (xmlStrstr(cur_node->name, BAD_CAST "Envelope"))
            {
                // cur_node里面的ns项保存了整个ns列表
                for(cur_ns = cur_node->ns; cur_ns; cur_ns = cur_ns->next)
                {
                    /* do register namespace */
                    if (xmlXPathRegisterNs(xpathCtx, cur_ns->prefix, cur_ns->href) != 0)
                    {
                        fprintf(stderr, "Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", cur_ns->prefix, cur_ns->href);
                    }
                }
            }
        }

        print_element_names(cur_node->children, xpathCtx);
    }
}

/**
 * register_namespaces:
 * @xpathCtx:		the pointer to an XPath context.
 * @nsList:		the list of known namespaces in
 *			"<prefix1>=<href1> <prefix2>=href2> ..." format.
 *
 * Registers namespaces from @nsList in @xpathCtx.
 *
 * Returns 0 on success and a negative value otherwise.
 */
int register_namespaces(xmlXPathContextPtr xpathCtx, const xmlChar *nsList)
{
    xmlChar *nsListDup;
    xmlChar *prefix;
    xmlChar *href;
    xmlChar *next;

    assert(xpathCtx);
    assert(nsList);

    nsListDup = xmlStrdup(nsList);
    if (nsListDup == NULL)
    {
        fprintf(stderr, "Error: unable to strdup namespaces list\n");
        return (-1);
    }

    next = nsListDup;
    while (next != NULL)
    {
        /* skip spaces */
        while ((*next) == ' ')
            next++;
        if ((*next) == '\0')
            break;

        /* find prefix */
        prefix = next;
        next = (xmlChar *)xmlStrchr(next, '=');
        if (next == NULL)
        {
            fprintf(stderr, "Error: invalid namespaces list format\n");
            xmlFree(nsListDup);
            return (-1);
        }
        *(next++) = '\0';

        /* find href */
        href = next;
        next = (xmlChar *)xmlStrchr(next, ' ');
        if (next != NULL)
        {
            *(next++) = '\0';
        }

        /* do register namespace */
        if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0)
        {
            fprintf(stderr, "Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", prefix, href);
            xmlFree(nsListDup);
            return (-1);
        }
    }

    xmlFree(nsListDup);
    return (0);
}

int main(int argc, char **argv)
{
    /* Parse command line and process file */
    if (argc != 4)
    {
        fprintf(stderr, "Error: wrong number of arguments.\n");
        usage(argv[0]);
        return (-1);
    }

    /* Init libxml */
    xmlInitParser();
    LIBXML_TEST_VERSION

    /* Do the main job */
    if (example4(argv[1], BAD_CAST argv[2], BAD_CAST argv[3]))
    {
        usage(argv[0]);
        return (-1);
    }

    /* Shutdown libxml */
    xmlCleanupParser();

    /*
     * this is to debug memory for regression tests
     */
    xmlMemoryDump();
    return 0;
}

/**
 * usage:
 * @name:		the program name.
 *
 * Prints usage information.
 */
static void
usage(const char *name)
{
    assert(name);

    fprintf(stderr, "Usage: %s <xml-file> <xpath-expr> <value>\n", name);
}

/**
 * example4:
 * @filename:		the input XML filename.
 * @xpathExpr:		the xpath expression for evaluation.
 * @value:		the new node content.
 *
 * Parses input XML file, evaluates XPath expression and update the nodes
 * then print the result.
 *
 * Returns 0 on success and a negative value otherwise.
 */
static int
example4(const char *filename, const xmlChar *xpathExpr, const xmlChar *value)
{
    xmlDocPtr doc;
    xmlXPathContextPtr xpathCtx;
    xmlXPathObjectPtr xpathObj;

    assert(filename);
    assert(xpathExpr);
    assert(value);

    /* Load XML document */
    // doc = xmlParseFile(filename);
    doc = xmlReadFile(filename, NULL, 0);
    if (doc == NULL)
    {
        fprintf(stderr, "Error: unable to parse file \"%s\"\n", filename);
        return (-1);
    }

    xmlNode *root_element = NULL;

    /*Get the root element node */
    root_element = xmlDocGetRootElement(doc);

    /* Create xpath evaluation context */
    xpathCtx = xmlXPathNewContext(doc);

    print_element_names(root_element, xpathCtx);
    if (xpathCtx == NULL)
    {
        fprintf(stderr, "Error: unable to create new XPath context\n");
        xmlFreeDoc(doc);
        return (-1);
    }

    /* Evaluate xpath expression */
    // xmlXPathRegisterNs(xpathCtx, BAD_CAST "SOAP-ENV", BAD_CAST "SOAP-ENV:");
    xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
    if (xpathObj == NULL)
    {
        fprintf(stderr, "Error: unable to evaluate xpath expression \"%s\"\n", xpathExpr);
        xmlXPathFreeContext(xpathCtx);
        xmlFreeDoc(doc);
        return (-1);
    }

    /* update selected nodes */
    update_xpath_nodes(xpathObj->nodesetval, value);

    /* Cleanup of XPath data */
    xmlXPathFreeObject(xpathObj);
    xmlXPathFreeContext(xpathCtx);

    /* dump the resulting document */
    xmlDocDump(stdout, doc);

    /* free the document */
    xmlFreeDoc(doc);

    return (0);
}

/**
 * update_xpath_nodes:
 * @nodes:		the nodes set.
 * @value:		the new value for the node(s)
 *
 * Prints the @nodes content to @output.
 */
static void
update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar *value)
{
    int size;
    int i;

    assert(value);
    size = (nodes) ? nodes->nodeNr : 0;

    /*
     * NOTE: the nodes are processed in reverse order, i.e. reverse document
     *       order because xmlNodeSetContent can actually free up descendant
     *       of the node and such nodes may have been selected too ! Handling
     *       in reverse order ensure that descendant are accessed first, before
     *       they get removed. Mixing XPath and modifications on a tree must be
     *       done carefully !
     */
    for (i = size - 1; i >= 0; i--)
    {
        assert(nodes->nodeTab[i]);

        xmlNodeSetContent(nodes->nodeTab[i], value);
        /*
         * All the elements returned by an XPath query are pointers to
         * elements from the tree *except* namespace nodes where the XPath
         * semantic is different from the implementation in libxml2 tree.
         * As a result when a returned node set is freed when
         * xmlXPathFreeObject() is called, that routine must check the
         * element type. But node from the returned set may have been removed
         * by xmlNodeSetContent() resulting in access to freed data.
         * This can be exercised by running
         *       valgrind xpath2 test3.xml '//discarded' discarded
         * There is 2 ways around it:
         *   - make a copy of the pointers to the nodes from the result set
         *     then call xmlXPathFreeObject() and then modify the nodes
         * or
         *   - remove the reference to the modified nodes from the node set
         *     as they are processed, if they are not namespace nodes.
         */
        if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL)
            nodes->nodeTab[i] = NULL;
    }
}

#else
int main(void)
{
    fprintf(stderr, "XPath support not compiled in\n");
    return 0;
}
#endif